package com.ruoyi.water.service;

import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.water.domain.dto.ApiResponse;
import com.ruoyi.water.domain.dto.AuthRequest;
import com.ruoyi.water.domain.dto.WaterToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * 盛帆节能平台令牌服务（基于若依RedisCache实现令牌缓存）
 * 功能：1.获取令牌 2.缓存令牌 3.刷新令牌 4.清除令牌
 */
@Service
@Slf4j
public class WaterTokenService {

    /** 若依已封装的Redis缓存工具类（直接注入使用） */
    @Autowired
    private RedisCache redisCache;

    /** HTTP请求工具（用于调用平台接口） */
    @Autowired
    private RestTemplate restTemplate;

    // -------------------------- 平台配置（从application.yml读取，需用户配置） --------------------------
    /** 令牌获取接口地址（文档4.1.1节） */
    @Value("${energy.api.base-url:}")
    private String baseUrl;

    /** 平台账号（由盛帆提供） */
    @Value("${energy.api.auth.username:}")
    private String username;

    /** 平台密码（由盛帆提供） */
    @Value("${energy.api.auth.password:}")
    private String password;

    /** 客户端ID（由盛帆提供，文档4.1.3节必填参数） */
    @Value("${energy.api.auth.client-id:}")
    private String clientId;

    /** 客户端密钥（由盛帆提供，文档4.1.3节必填参数） */
    @Value("${energy.api.auth.client-secret:}")
    private String clientSecret;

    // -------------------------- Redis键配置 --------------------------
    /** Redis存储access_token的键（避免与其他缓存冲突） */
    private static final String REDIS_ACCESS_TOKEN_KEY = "energy_platform:access_token";

    /** Redis存储refresh_token的键 */
    private static final String REDIS_REFRESH_TOKEN_KEY = "energy_platform:refresh_token";

    /** 令牌提前过期时间（秒）：避免令牌在请求中过期，提前60秒失效 */
    private static final int TOKEN_EXPIRE_ADVANCE = 60;

    // -------------------------- 核心方法 --------------------------

    /**
     * 获取有效访问令牌（优先从Redis取，无则请求接口，失败则刷新）
     * @return 有效access_token（可直接用于后续接口调用）
     * @throws Exception 令牌获取/刷新失败时抛出异常
     */
    public String getEffectiveAccessToken() throws Exception {
        // 1. 先从Redis查询未过期的access_token
        String accessToken = getTokenFromRedis(REDIS_ACCESS_TOKEN_KEY);
        if (accessToken != null) {
            log.info("从Redis获取到有效access_token，直接返回");
            return accessToken;
        }

        // 2. Redis无有效令牌，调用平台接口【密码模式】获取新令牌
        log.info("Redis无有效令牌，调用认证接口（密码模式）获取新令牌");
        AuthRequest passwordAuthReq = new AuthRequest();
        passwordAuthReq.setUsername(username);
        passwordAuthReq.setPassword(password);
        passwordAuthReq.setGrantType("password");
        passwordAuthReq.setClientId(clientId);
        passwordAuthReq.setClientSecret(clientSecret);
        WaterToken newToken = requestToken(passwordAuthReq);
        if (newToken != null) {
            saveTokenToRedis(newToken); // 存储到Redis
            log.info("新令牌获取成功，有效期：{}秒", newToken.getExpires_in());
            return newToken.getAccess_token();
        }

        // 3. 新令牌获取失败，尝试用refresh_token【刷新模式】刷新
        log.warn("新令牌获取失败，尝试用refresh_token刷新");
        String refreshToken = getTokenFromRedis(REDIS_REFRESH_TOKEN_KEY);
        if (refreshToken == null) {
            throw new IllegalArgumentException("refresh_token不存在，需重新配置账号密码");
        }

        AuthRequest refreshAuthReq = new AuthRequest();
        refreshAuthReq.setGrantType("refresh_token");
        refreshAuthReq.setRefresh_token(refreshToken);
        refreshAuthReq.setClientId(clientId);
        refreshAuthReq.setClientSecret(clientSecret);
        WaterToken refreshedToken = requestToken(refreshAuthReq);
        if (refreshedToken == null) {
            throw new RestClientException("refresh_token已过期，请联系平台重置客户端信息");
        }

        // 4. 刷新成功，更新Redis缓存
        saveTokenToRedis(refreshedToken);
        log.info("refresh_token刷新成功，新令牌有效期：{}秒", refreshedToken.getExpires_in());
        return refreshedToken.getAccess_token();
    }

    /**
     * 清除Redis中的令牌（如登出、切换账号场景）
     */
    public void clearTokenCache() {
        redisCache.deleteObject(REDIS_ACCESS_TOKEN_KEY);
        redisCache.deleteObject(REDIS_REFRESH_TOKEN_KEY);
        log.info("已清除Redis中的access_token和refresh_token");
    }

    // -------------------------- 私有工具方法 --------------------------

    private WaterToken requestToken(AuthRequest authRequest) {
        // 1. 校验必要参数
        if (authRequest == null) {
            log.error("认证请求参数为空");
            return null;
        }
        if ("password".equals(authRequest.getGrantType())) {
            if (authRequest.getUsername() == null || authRequest.getPassword() == null) {
                log.error("密码模式下，username/password不能为空");
                return null;
            }
        } else if ("refresh_token".equals(authRequest.getGrantType())) {
            if (authRequest.getRefresh_token() == null) {
                log.error("刷新令牌模式下，refresh_token不能为空");
                return null;
            }
        }
        if (authRequest.getClientId() == null || authRequest.getClientSecret() == null) {
            log.error("clientId/clientSecret不能为空");
            return null;
        }

        //url拼接
        String authUrl = UriComponentsBuilder.fromHttpUrl(baseUrl)
                .path("/api/open/auth/oauth/token")
                .toUriString();

        // 2. 构建JSON请求头 + 请求体
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<AuthRequest> request = new HttpEntity<>(authRequest, headers);

        try {
            // 3. 发送POST请求，用ParameterizedTypeReference解析泛型响应
            ResponseEntity<ApiResponse<WaterToken>> response = restTemplate.exchange(
                    authUrl,
                    HttpMethod.POST,
                    request,
                    new ParameterizedTypeReference<ApiResponse<WaterToken>>() {}
            );

            // 4. 校验响应
            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                ApiResponse<WaterToken> respBody = response.getBody();
                if (Boolean.TRUE.equals(respBody.getSuccess()) && respBody.getData() != null) {
                    return respBody.getData();
                }
                log.error("令牌接口返回失败：{}", respBody.getMessage());
            } else {
                log.error("令牌接口请求失败，状态码：{}", response.getStatusCode());
            }
        } catch (RestClientException e) {
            log.error("调用令牌接口异常：", e);
        }
        return null;
    }

    /**
     * 从Redis获取令牌（基于若依RedisCache）
     * @param redisKey Redis键
     * @return 令牌字符串（过期/不存在返回null）
     */
    private String getTokenFromRedis(String redisKey) {
        try {
            return redisCache.getCacheObject(redisKey);
        } catch (Exception e) {
            log.error("从Redis获取令牌异常，key={}", redisKey, e);
            return null;
        }
    }

    /**
     * 将令牌存储到Redis（设置过期时间，基于若依RedisCache）
     * @param tokenInfo 令牌信息DTO
     */
    private void saveTokenToRedis(WaterToken tokenInfo) {
        // 1. 存储access_token：有效期 = 平台返回时间 - 提前过期时间（最低保留60秒）
        int accessExpire = Math.max(tokenInfo.getExpires_in() - TOKEN_EXPIRE_ADVANCE, 60);
        redisCache.setCacheObject(
                REDIS_ACCESS_TOKEN_KEY,
                tokenInfo.getAccess_token(),
                accessExpire,
                java.util.concurrent.TimeUnit.SECONDS
        );

        // 2. 存储refresh_token：默认7天有效期（平台未返回，按常规配置）
        int refreshExpire = 7 * 24 * 3600;
        redisCache.setCacheObject(
                REDIS_REFRESH_TOKEN_KEY,
                tokenInfo.getRefresh_token(),
                refreshExpire,
                java.util.concurrent.TimeUnit.SECONDS
        );

        log.info("令牌已存储到Redis：access_token有效期{}秒", accessExpire);
    }
}